﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using NcfDB.Model;
using NcfDB.Exception;

namespace NcfDB
{
    class CoreDBSystem
    {
        //Configuration
        /// <summary>
        /// NcfDB主目录
        /// </summary>
        public const string diskFilePathPrefix = "./NcfDB/";
        /// <summary>
        /// NcfDB系统元信息
        /// </summary>
        protected const string metaDataFilePath = "MetaData.dat";
        public static Encoding systemEncoding = Encoding.UTF8;
        public const int maxDbCount = 10;

        //Properties
        protected static CoreDBSystem instance = null;
        protected static Dictionary<string, Database> databases;
        protected int _dbCount = 0;
        public int dbCount { get { return _dbCount; } }

        public CoreDBSystem()
        {
            if (CoreDBSystem.instance != null) throw new CoreDBSystemException("Only one instance can be created!");
            if (!File.Exists(CoreDBSystem.getFilePath(CoreDBSystem.metaDataFilePath))) this.format();
            this.init();
            this.save();
            CoreDBSystem.instance = this;
        }

        public static CoreDBSystem getInstance()
        {
            return CoreDBSystem.instance != null ? CoreDBSystem.instance  : CoreDBSystem.instance = new CoreDBSystem();
        }
        /// <summary>
        /// 初始化NcfDB
        /// </summary>
        public void init()
        {
            databases = new Dictionary<string, Database>();
            if (!Directory.Exists(CoreDBSystem.diskFilePathPrefix))
            {
                Directory.CreateDirectory(CoreDBSystem.diskFilePathPrefix);
            }
            string metaDataPath = CoreDBSystem.getFilePath(CoreDBSystem.metaDataFilePath);
            FileStream fs = new FileStream(metaDataPath, FileMode.Open, FileAccess.Read);
            this._dbCount = Utility.readIntFromStream(ref fs);
            for (int i = 0; i < this._dbCount; i++)
            {
                string dbName = Utility.readStringFromStream(ref fs, Database.dbNameBytesLimit);
                Database db = new Database();
                db = Database.readFromStream(dbName);
                db.setDbName(dbName);
                databases.Add(dbName, db);
            }
            fs.Close();
        }
        public static List<string> getDBName()
        {
            return databases.Keys.ToList<string>();
        }
        /// <summary>
        /// 格式化NcfDB（重建系统元信息）
        /// </summary>
        public void format()
        {
            this._dbCount = 0;
            databases = new Dictionary<string, Database>();
            this.saveMeta();
        }

        public void save()
        {
            //强制刷新dbCount
            this._dbCount = databases.Count;
            this.saveDBs();
            this.saveMeta();
        }

        protected void saveMeta()
        {
            string metaDataPath = CoreDBSystem.getFilePath(CoreDBSystem.metaDataFilePath);
            if (!Directory.Exists(CoreDBSystem.diskFilePathPrefix))
            {
                Directory.CreateDirectory(CoreDBSystem.diskFilePathPrefix);
            }
            FileStream fs = new FileStream(metaDataPath, FileMode.Create, FileAccess.ReadWrite);//如果文件存在则会覆盖原文件
            Utility.writeBytes(BitConverter.GetBytes(this.dbCount), ref fs);
            foreach (string dbName in databases.Keys)
            {
                Utility.writeStringWithFilling(dbName, Database.dbNameBytesLimit, ref fs);
            }
            fs.Flush();
            fs.Close();
        }

        /// <summary>
        /// 获取NcfDB的文件路径
        /// </summary>
        /// <param name="filePath">相对于主目录的文件路径</param>
        /// <returns></returns>
        public static string getFilePath(string filePath)
        {
            return diskFilePathPrefix + filePath;
        }

        /// <summary>
        /// 创建一个新表,DBname为空时表示当前库
        /// </summary>
        /// <param name="tableName"></param>
        /// <param name="fields"></param>
        /// <param name="DBname"></param>
        /// <returns></returns>
        public static bool createTable(string tableName, Dictionary<string, FieldProperty> fields, string DBName = null)
        {
            Database db = new Database();
            db = getDB(DBName);
            Table table = new Table();
            table.Name = tableName;
            foreach (var field in fields)
            {
                table.addFieldProperty(field.Value);
            }
            db.addTable(table);
            return true;
        }

        /// <summary>
        /// 创建一个新数据库
        /// </summary>
        /// <param name="DBname"></param>
        /// <returns></returns>
        public static bool createDB(string DBname)
        {
            Database db = new Database();
            db.setDbName(DBname);
            databases.Add(DBname, db);
            return true;
        }

        /// <summary>
        /// 获取表中字段大小
        /// </summary>
        /// <param name="tableName"></param>
        /// <param name="DBname"></param>
        /// <returns></returns>
        public static int getFieldSize(string tableName, string DBname) { return -1; }

        /// <summary>
        /// 按照序号查询字段名 [这个暂时没用好像]
        /// </summary>
        /// <param name="tableName"></param>
        /// <param name="index"></param>
        /// <param name="DBname"></param>
        /// <returns></returns>
        public static string getFieldNameByIndex(string tableName, int index, string DBname) { return null; }

        /// <summary>
        /// 获取表中所有字段名
        /// </summary>
        /// <param name="tableName"></param>
        /// <param name="DBname"></param>
        /// <returns></returns>
        public static List<string> getAllFieldsName(string tableName, string DBname = null)
        {
            return getDB(DBname).getTable(tableName).fieldOrder;
        }

        /// <summary>
        /// 按照字段名获取字段数据类型
        /// </summary>
        /// <param name="tableName"></param>
        /// <param name="fieldName"></param>
        /// <param name="DBname"></param>
        /// <returns></returns>
        public static FieldType getColumnTypeByName(string tableName, string fieldName, string DBname)
        {
            return getDB(DBname).getTable(tableName).getFieldProperty(fieldName).Type;
        }

        /// <summary>
        /// 插入行
        /// </summary>
        /// <param name="tableName"></param>
        /// <param name="data"></param>
        /// <param name="DBname"></param>
        /// <returns></returns>
        public static bool insertRow(string tableName, Dictionary<string, object> data, string DBName)
        {
            Database db = new Database();
            db = getDB(DBName);
            Table table = new Table();
            table = db.getTable(tableName);
            Row row = new Row();
            foreach (string field in table.fieldOrder)
            {
                if (!data.ContainsKey(field))
                    data.Add(field, null);
            }
            row.setData(data);
            try
            {
                table.addRow(row);
            }
            catch
            {
                return false;
            }
            return true;
        }

        /// <summary>
        /// 获取数据库,参数为空时获取当前数据库
        /// </summary>
        /// <param name="DBName"></param>
        /// <returns></returns>
        public static Database getDB(string DBName)
        {
            if (databases.ContainsKey(DBName))
                return databases[DBName];
            throw new CoreDBSystemException("指定的数据库不存在");
        }

        /// <summary>
        /// 删除行
        /// </summary>
        /// <param name="tableName"></param>
        /// <param name="index"></param>
        /// <param name="DBName"></param>
        /// <returns></returns>
        public static bool deleteRow(string tableName, int index, string DBName)
        {
            Database db = new Database();
            db = getDB(DBName);
            db.getTable(tableName).deleteRow(db.getTable(tableName).Rows[index]);
            return true;
        }

        /// <summary>
        /// 编辑字段
        /// </summary>
        /// <param name="tableName"></param>
        /// <param name="fieldName"></param>
        /// <param name="newField"></param>
        /// <param name="DBName"></param>
        /// <returns></returns>
        public static bool editTable(string tableName, Dictionary<string, FieldProperty> data, string DBName)
        {
            string key;
            Database db = new Database();
            db = getDB(DBName);
            Table table = new Table();
            table = db.getTable(tableName);
            foreach (var fp in data)
            {
                key = fp.Key;
                if (table.getFieldProperty(key) != null)
                    table.setFieldProperty(key, fp.Value);
            }
            return true;
        }

        /// <summary>
        /// 重命名表
        /// </summary>
        /// <param name="oldTableName"></param>
        /// <param name="newTableName"></param>
        /// <param name="DBName"></param>
        /// <returns></returns>
        public static bool renameTable(string oldTableName, string newTableName, string DBName)
        {
            Database db = new Database();
            db = getDB(DBName);
            Table table = new Table();
            table = db.getTable(oldTableName);
            table.Name = newTableName;
            db.deleteTable(oldTableName);
            db.addTable(table);
            return true;
        }

        /// <summary>
        /// 删除表
        /// </summary>
        /// <param name="tableName"></param>
        /// <param name="DBName"></param>
        /// <returns></returns>
        public static bool dropTable(string tableName, string DBName)
        {
            Database db = new Database();
            db = getDB(DBName);
            db.deleteTable(tableName);
            return true;
        }

        /// <summary>
        /// 删除数据库
        /// </summary>
        /// <param name="DBName"></param>
        /// <returns></returns>
        public static bool dropDB(string DBName)
        {
            string dbfPath, datPath;
            if (databases.ContainsKey(DBName))
            {
                Database db = new Database();
                db.setDbName(DBName);
                dbfPath = db.getFilePath(true);
                datPath = db.getFilePath(false);
                if (File.Exists(dbfPath))
                    File.Delete(dbfPath);
                if (File.Exists(datPath))
                    File.Delete(datPath);
                databases.Remove(DBName);
            }
            return true;
        }

        /// <summary>
        /// 更新行数据
        /// </summary>
        /// <param name="tableName">表名</param>
        /// <param name="rowID">行序号</param>
        /// <param name="data">数据键值对</param>
        /// <returns></returns>
        public static bool updateRow(string tableName, int rowID, Dictionary<string, object> data, string DBName = null)
        {
            Database db = new Database();
            db = getDB(DBName);
            Table table = new Table();
            table = db.getTable(tableName);
            foreach (var d in data)
            {
                table.Rows[rowID][d.Key] = d.Value;
            }
            return true;
        }

        /// <summary>
        /// 数据库写回文件
        /// </summary>
        /// <returns></returns>
        protected void saveDBs()
        {
            foreach (Database database in databases.Values) database.save();
        }
        /// <summary>
        /// 重命名数据库
        /// </summary>
        /// <param name="oldDBName"></param>
        /// <param name="newDBName"></param>
        public static void renameDB(string oldDBName,string newDBName)
        {
            if(newDBName != null)
            {
                databases.Add(newDBName, databases[oldDBName]);
                databases[newDBName].setDbName(newDBName);
                CoreDBSystem.dropDB(oldDBName);
            }
        }
    }
}
